Blocks 与事件监听 (Blocks and Event Listeners)
Gradio Blocks
是一种低级别 API,它允许您比 Interface
API 更灵活地创建自定义的 Web 应用和 Demo。使用 Blocks
,您可以更精细地控制组件的布局、触发函数执行的事件,以及数据流。
Blocks 结构
使用 Blocks
的基本方式是在一个 with gr.Blocks() as demo:
上下文管理器中定义您的应用。
import gradio as gr
def greet(name):
return "Hello " + name + "!"
with gr.Blocks() as demo:
name_textbox = gr.Textbox(label="Name")
output_textbox = gr.Textbox(label="Output Box")
greet_button = gr.Button("Greet")
greet_button.click(fn=greet, inputs=name_textbox, outputs=output_textbox, api_name="greet")
demo.launch()
解读:
with gr.Blocks() as demo:
:所有Blocks
应用的代码都包含在这个with
语句块中。Blocks
实例通常命名为demo
。- 组件 (Components):这些与
Interface
中使用的组件相同。然而,在Blocks
中,组件在with
语句块中被创建时会自动添加到应用中,而不是传递给某个构造函数。 - 事件监听器 (Event Listeners):如
greet_button.click(...)
,事件监听器定义了应用内的数据流。在上面的例子中,监听器将name_textbox
(输入)和output_textbox
(输出)通过greet
函数连接起来。当greet_button
被点击时,这个数据流就会被触发。与Interface
类似,一个事件监听器可以有多个输入或输出。
装饰器语法
您也可以使用装饰器语法来附加事件监听器,这样可以省略 fn
参数,直接指定 inputs
和 outputs
:
import gradio as gr
with gr.Blocks() as demo:
name_textbox = gr.Textbox(label="Name")
output_textbox = gr.Textbox(label="Output Box")
greet_button = gr.Button("Greet")
@greet_button.click(inputs=name_textbox, outputs=output_textbox)
def greet(name):
return "Hello " + name + "!"
demo.launch()
事件监听器与交互性 (Event Listeners and Interactivity)
在上面的例子中,您会发现 name_textbox
是可编辑的,而 output_textbox
是不可编辑的。这是因为任何作为事件监听器输入的组件都会默认变为可交互的。相反,由于 output_textbox
仅作为输出,Gradio 会默认使其不可交互。
您可以通过组件的 interactive
布尔参数来覆盖这种默认行为,例如 gr.Textbox(interactive=True)
。
output_textbox = gr.Textbox(label="Output", interactive=True)
注意:如果一个 Gradio 组件既不是输入也不是输出,会发生什么?如果组件在构造时带有默认值,则假定它用于显示内容,并呈现为非交互式。否则,它将呈现为交互式。同样,这种行为可以通过指定 interactive
参数的值来覆盖。
事件监听器的类型 (Types of Event Listeners)
不同的组件支持不同类型的事件监听器。例如,gr.Button
支持 click
事件,gr.Textbox
支持 change
(当内容改变时) 和 submit
(当用户按下 Enter 键时) 事件,而 gr.Video
支持 play
事件 (当用户按下播放按钮时)。
查看下面的示例,其中 welcome
函数由在 inp
文本框中键入内容触发,这是因为使用了 change()
事件监听器:
import gradio as gr
def welcome(name):
return f"Welcome to Gradio, {name}!"
with gr.Blocks() as demo:
gr.Markdown(
'''
# Hello World!
Start typing below to see the output.
'''
)
inp = gr.Textbox(placeholder="What is your name?")
out = gr.Textbox()
inp.change(fn=welcome, inputs=inp, outputs=out)
demo.launch()
请查阅各个组件的文档以了解它们支持的特定事件监听器。
多数据流 (Multiple Data Flows)
与 Interface
不同,Blocks
应用不限于单一的数据流。您可以创建多个独立的数据流,连接应用中的各个组件。
import gradio as gr
def increase(num):
return num + 1
with gr.Blocks() as demo:
a_number = gr.Number(label="a")
b_number = gr.Number(label="b")
button_atob = gr.Button("a > b")
button_btoa = gr.Button("b > a")
button_atob.click(fn=increase, inputs=a_number, outputs=b_number)
button_btoa.click(fn=increase, inputs=b_number, outputs=a_number)
demo.launch()
在这个例子中,a_number
可以作为 b_number
的输入,反之亦然。随着应用变得更复杂,您可能会有许多数据流连接各种组件。
下面是一个"多步骤"Demo的例子,其中一个模型(语音转文本)的输出被送入下一个模型(情感分类器):
from transformers import pipeline
import gradio as gr
asr_pipeline = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
classifier_pipeline = pipeline("text-classification")
def speech_to_text(speech_audio):
if speech_audio is None:
return "", ""
text_output = asr_pipeline(speech_audio)["text"]
return text_output
def text_to_sentiment(text_input):
if not text_input:
return ""
return classifier_pipeline(text_input)[0]["label"]
with gr.Blocks() as demo:
audio_file_input = gr.Audio(type="filepath", label="Upload Audio")
text_output_stt = gr.Textbox(label="Speech to Text")
label_output_sentiment = gr.Label(label="Sentiment")
button_recognize = gr.Button("Recognize Speech")
button_classify = gr.Button("Classify Sentiment")
button_recognize.click(speech_to_text, inputs=audio_file_input, outputs=text_output_stt)
button_classify.click(text_to_sentiment, inputs=text_output_stt, outputs=label_output_sentiment)
demo.launch()
函数输入:列表 vs. 字典 (Function Inputs: List vs. Dict)
当事件监听器函数需要多个输入组件的值时,您的函数有两种方式接收这些值:
- 作为参数列表 (list of arguments)。
- 作为单个字典 (dictionary),其中键是组件对象,值是组件的值。
import gradio as gr
with gr.Blocks() as demo:
a = gr.Number(label="a")
b = gr.Number(label="b")
with gr.Row():
add_button = gr.Button("Add")
subtract_button = gr.Button("Subtract")
c = gr.Number(label="sum")
def add_numbers(num1, num2):
return num1 + num2
add_button.click(fn=add_numbers, inputs=[a, b], outputs=c)
def subtract_numbers_dict(data):
return data[a] - data[b]
# 注意 inputs={a, b} 是一个集合,它会被转换为字典
subtract_button.click(fn=subtract_numbers_dict, inputs={a, b}, outputs=c)
demo.launch()
- 对于
add_button
,我们将输入作为列表[a, b]
传递。函数add_numbers
按顺序接收这些输入作为参数num1
和num2
。 - 对于
subtract_button
,我们将输入作为集合{a, b}
传递 (Gradio 内部会将其解释为字典形式的inputs={a:a, b:b}
效果,函数接收时是data[<Component a>]
和data[<Component b>]
)。函数subtract_numbers_dict
接收一个名为data
的字典参数,其中键是输入组件对象,值是这些组件的当前值。
对于具有许多输入组件的函数,字典语法可能更易于管理。
函数输出:列表 vs. 字典 (Function Outputs: List vs. Dict)
类似地,您可以为多个输出组件返回值:
- 作为值的列表 (list of values)。
- 作为字典 (dictionary),其中键是组件对象,值是它们的新值。
返回值的列表(按顺序对应输出组件):
import gradio as gr
with gr.Blocks() as demo:
food_box = gr.Number(value=10, label="Food Count")
status_box = gr.Textbox(label="Status")
def eat_food(current_food):
if current_food > 0:
return current_food - 1, "full"
else:
return 0, "hungry"
gr.Button("Eat").click(
fn=eat_food,
inputs=food_box,
outputs=[food_box, status_box]
)
demo.launch()
在这里,eat_food
函数返回两个值,分别对应 food_box
和 status_box
。
返回字典(允许跳过某些组件的更新):
import gradio as gr
with gr.Blocks() as demo:
food_box = gr.Number(value=10, label="Food Count")
status_box = gr.Textbox(label="Status")
def eat_food_dict(current_food):
if current_food > 0:
return {food_box: current_food - 1, status_box: "full"}
else:
# 只更新 status_box,food_box 保持不变(如果使用 gr.skip() 则更明确)
# 或者,如果 food_box 不在返回的字典中,Gradio 也会跳过对它的更新
return {status_box: "hungry"}
gr.Button("Eat").click(
fn=eat_food_dict,
inputs=food_box,
outputs=[food_box, status_box] # 必须声明所有可能的输出组件
)
demo.launch()
注意当没有食物时,我们只更新了 status_box
。我们跳过了对 food_box
组件的更新,因为它没有包含在返回的字典的键中。 当事件监听器影响许多组件,或有条件地影响某些输出而不影响其他输出时,字典返回非常有用。 请记住,即使使用字典返回,我们也需要在事件监听器中指定所有可能的输出组件。
更新组件配置 (Updating Component Configurations)
事件监听函数的返回值通常是相应输出组件的更新值。有时,我们希望同时更新组件的配置,例如其可见性、行数等。在这种情况下,我们返回一个新的组件实例,设置我们想要更改的属性。
import gradio as gr
def change_textbox_config(choice):
if choice == "short":
return gr.Textbox(lines=2, visible=True, placeholder="Short essay...")
elif choice == "long":
return gr.Textbox(lines=8, visible=True, value="Lorem ipsum dolor sit amet", placeholder="Long essay...")
else:
return gr.Textbox(visible=False)
with gr.Blocks() as demo:
radio_choice = gr.Radio(
["short", "long", "none"], label="What kind of essay would you like to write?"
)
text_editor = gr.Textbox(lines=2, interactive=True, show_copy_button=True)
radio_choice.change(fn=change_textbox_config, inputs=radio_choice, outputs=text_editor)
demo.launch()
可以看到,我们可以通过返回一个新的 gr.Textbox(...)
实例来配置文本框本身。value=
参数仍然可以用来更新值以及组件配置。任何我们没有设置的参数将保留它们之前的值。
不改变组件值 (Not Changing a Component's Value)
在某些情况下,您可能希望保持组件的值不变。Gradio 包含一个特殊的值 gr.skip()
,您可以从函数中返回它。返回 gr.skip()
将使输出组件(或多个组件)的值保持原样。
import random
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
clear_button = gr.Button("Clear")
skip_button = gr.Button("Skip Update") # Renamed for clarity
random_button = gr.Button("Random")
number_outputs = [gr.Number(label="Num 1"), gr.Number(label="Num 2")]
clear_button.click(lambda : (None, None), outputs=number_outputs)
skip_button.click(lambda : (gr.skip(), gr.skip()), outputs=number_outputs)
random_button.click(lambda : (random.randint(0, 100), random.randint(0, 100)), outputs=number_outputs)
demo.launch()
注意返回 None
(通常会将组件的值重置为空状态)与返回 gr.skip()
(保持组件值不变)之间的区别。
提示:如果您有多个输出组件,并且希望保持所有组件的值不变,您可以只返回一个 gr.skip()
,而不是为每个元素返回一个包含 gr.skip()
的元组。Gradio 会将单个 gr.skip()
应用于所有列出的输出组件。
连续运行事件 (Running Events Consecutively)
您可以使用事件监听器的 .then()
方法来连续运行事件。这将在前一个事件完成后运行下一个事件。这对于分多步更新组件的事件非常有用。
例如,在下面的聊天机器人示例中,我们首先立即用用户消息更新聊天机器人,然后在模拟延迟后用计算机响应更新聊天机器人。
import gradio as gr
import random
import time
with gr.Blocks() as demo:
chatbot_display = gr.Chatbot()
message_input = gr.Textbox(label="Your message")
clear_button = gr.Button("Clear Chat")
def user_sends_message(user_message, history):
return "", history + [[user_message, None]]
def bot_responds(history):
bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
time.sleep(1) # Simulate delay
history[-1][1] = bot_message
return history
message_input.submit(
fn=user_sends_message,
inputs=[message_input, chatbot_display],
outputs=[message_input, chatbot_display],
queue=False # For responsiveness
).then(
fn=bot_responds,
inputs=chatbot_display,
outputs=chatbot_display
)
clear_button.click(lambda: None, outputs=chatbot_display, queue=False)
demo.launch()
事件监听器的 .then()
方法无论前一个事件是否引发任何错误,都会执行后续事件。如果您只想在先前事件成功执行后才运行后续事件,请使用 .success()
方法,它接受与 .then()
相同的参数。
将多个触发器绑定到一个函数 (Binding Multiple Triggers to a Function)
通常,您可能希望将多个触发器绑定到同一个函数。例如,您可能希望允许用户单击提交按钮或按 Enter 键来提交表单。您可以使用 gr.on()
方法,并将触发器列表传递给 triggers
参数。
import gradio as gr
with gr.Blocks() as demo:
name_input = gr.Textbox(label="Name")
output_display = gr.Textbox(label="Output Box")
greet_button = gr.Button("Greet")
# This textbox is just to show event data target
trigger_info_display = gr.Textbox(label="Trigger Source Info")
def greet_user(name_val, evt_data: gr.EventData):
# evt_data.target can give info about the component that triggered the event
return f"Hello {name_val}!", f"Triggered by: {evt_data.target.__class__.__name__}"
def clear_name_field(evt_data: gr.EventData): # evt_data is optional here
return ""
gr.on(
triggers=[name_input.submit, greet_button.click],
fn=greet_user,
inputs=name_input,
outputs=[output_display, trigger_info_display],
).then(fn=clear_name_field, outputs=[name_input]) # Clear name field after greeting
demo.launch()
您也可以使用装饰器语法:
import gradio as gr
with gr.Blocks() as demo:
name_input = gr.Textbox(label="Name")
output_display = gr.Textbox(label="Output Box")
greet_button = gr.Button("Greet")
@gr.on(triggers=[name_input.submit, greet_button.click], inputs=name_input, outputs=output_display)
def greet_user_decorator(name_val):
return f"Hello {name_val}!"
demo.launch()
您可以使用 gr.on
通过绑定到实现了 change
事件的组件的该事件来创建"实时"事件。如果您没有指定任何触发器,该函数将自动绑定到所有具有 change
事件的输入组件的 change
事件(例如 gr.Textbox
有 change
事件,而 gr.Button
没有)。
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
slider1 = gr.Slider(1, 10, label="Num 1")
slider2 = gr.Slider(1, 10, label="Num 2")
slider3 = gr.Slider(1, 10, label="Num 3")
sum_output = gr.Number(label="Sum")
# Will trigger on change of any slider if triggers is not specified
# Or explicitly: @gr.on(triggers=[slider1.change, slider2.change, slider3.change], ...)
@gr.on(inputs=[slider1, slider2, slider3], outputs=sum_output)
def calculate_sum(a, b, c):
return a + b + c
demo.launch()
您可以像常规事件监听器一样,在 gr.on
后面跟上 .then
。这个方便的方法应该能帮您省去编写大量重复代码的麻烦!
直接绑定组件值 (Binding a Component Value Directly to a Function of Other Components)
如果您希望将一个组件的值设置为始终是其他组件值的函数计算结果,可以使用以下简写方式:
import gradio as gr
with gr.Blocks() as demo:
num1 = gr.Number(label="Number 1")
num2 = gr.Number(label="Number 2")
# product's value will automatically update when num1 or num2 changes
product = gr.Number(lambda a, b: a * b, inputs=[num1, num2], label="Product")
demo.launch()
这在功能上等同于:
import gradio as gr
with gr.Blocks() as demo:
num1 = gr.Number(label="Number 1")
num2 = gr.Number(label="Number 2")
product = gr.Number(label="Product")
def update_product(a, b):
return a * b
# Bind to change events of inputs and also demo.load to initialize
gr.on(
triggers=[num1.change, num2.change, demo.load],
fn=update_product,
inputs=[num1, num2],
outputs=product
)
demo.launch()
这种简写方式使得创建响应式 UI 元素更加便捷。